package p05_BeanFactory后处理器;

import org.mybatis.spring.mapper.MapperFactoryBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import p05_BeanFactory后处理器.mapper.Mapper1;

import java.io.IOException;

/**
 * @Mapper接口的解析 spring如何解析@Mapper的
 * 已知:MapperFactoryBean放入容器后，会根据Mapper接口创建Mapper对象(动态代理)
 * Mybatis有一个自动装配类，会把一个BeanFactory后处理器放入容器，该后处理器就负责解析@Mapper注解，并创建对应的MapperFactoryBean放入容器
 * 所以：这个解析MapperFactoryBean的后处理器原理根ComponentScan注解差不多，也是扫描包，扫到Mapper接口就创建一个MapperFactoryBean放入容器
 * 这里我们模拟了Mybatis的BeanFactory后处理器，该后处理器没有实现BeanFactoryPostProcessor，而是实现了BeanDefinitionRegistryPostProcessor
 * BeanDefinitionRegistryPostProcessor也是个后处理器，BeanFactoryPostProcessor的子类，加入容器后，初始化时会自动执行其两个方法
 * 本节知识点：
 *    Mapper接口被Spring管理的本质：实际是被作为MapperFactoryBean(封装了Mapper接口，能得到一些信息比如sql)注册到容器中
 */
public class _4_注解Mapper {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean(ConfigurationClassPostProcessor.class);
        //给容器注册SqlSessionFactory
        context.registerBean("config", Config.class);
        //解析@Mapper的beanFactory的后处理器
        context.registerBean(MyMapper.class);
        context.refresh();

        //这里可以去查询数据库，说明我们的mapper生效了(数据库配置按照自己实际来哦~)
        Mapper1 mapper1 = context.getBean("mapper1", Mapper1.class);
        mapper1.count();
    }

    /**
     * 模拟Mybatis解析Mapper注解的后处理器
     * spring发现后处理器是BeanDefinitionRegistryPostProcessor类型时，会调用其两个方法(postProcessBeanDefinitionRegistry, postProcessBeanFactory)
     * postProcessBeanDefinitionRegistry方法接收的参数是BeanDefinitionRegistry，可以直接注册bean定义，而不用类型转换
     */
    static class MyMapper implements BeanDefinitionRegistryPostProcessor {
        //运行这个后处理器，会先调用这个方法
        @Override
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
            //三个组件，前两个自定义BeanFactory后处理器里介绍了
            AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
            PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
            try {
                //得到mapper接口资源,这里为了方便资源通配符写死(实际开发中，@MapperScan注解会指定，或者根据接口有没有加@Mapper)
                Resource[] resources = resolver.getResources("classpath:p05_BeanFactory后处理器/mapper/**/*.class");

                for (Resource resource : resources) {
                    MetadataReader metadataReader = factory.getMetadataReader(resource);
                    //是接口类型才接着往下面执行
                    if (metadataReader.getClassMetadata().isInterface()) {
                        //没扫描到一个Mapper接口，就往容器中放一个MapperFactoryBean(MapperFactoryBean的作用前面已经讲了，就是根据接口使用动态代理生成实现类)
                        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
                                .genericBeanDefinition(MapperFactoryBean.class)     //哪个类的bean定义
                                .addConstructorArgValue(metadataReader.getClassMetadata().getClassName())   //创建bean时的构造器参数类型
                                .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)   //根据类型自动装配，将来会从容器中找到sqlSessionFactory进行注入
                                .getBeanDefinition();
                        //bean定义的名字(有点高级)
                        AbstractBeanDefinition db = BeanDefinitionBuilder.genericBeanDefinition(metadataReader.getClassMetadata().getClassName()).getBeanDefinition();
                        String beanName = generator.generateBeanName(db, beanFactory);
                        //注册bean
                        beanFactory.registerBeanDefinition(beanName, beanDefinition);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        //再调用这个方法
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        }
    }

}
